//<%@ taglib prefix="jj" uri="http://jaxson.org/base" %>

//<jj:import>
org.jaxson.util.collections.Map;
org.jaxson.util.collections.MappedList;
//</jj:import>

namespace( "org.jaxson" );

TransactionalEvent = function(name, url, vetoable, global){
	
	var suffixes = [".attempt", ".attempted", ".succeeded", ".failed"];
	var events = [];
	
	for( i in suffixes ){
		events.push({name: name + suffixes[i], url: url, vetoable: vetoable, global: global, tx: true, basename: name} );
	}
	
	return {events: events};
};

EventDispatcher = new function(){
	
	var map = new org.jaxson.util.collections.Map();
	var handlerCache = new org.jaxson.util.collections.MappedList();
	var eventCache = new org.jaxson.util.collections.MappedList();
	
	function methodName(evt){
		var tokenized = evt.split( "\." );
		var method = "";
		for( j in tokenized ){
			if( j == 0 ){
				method += tokenized[j];
				continue;
			}

			method += tokenized[j].charAt(0).toUpperCase() + tokenized[j].substring(1);
		}
		
		return method;
	}
	
	var that ={
		register : function( event, source ){
		
			var wrapper = {
				event: event,
				source: source
			};

			map.put( event.name, wrapper );
			
			// bind cached listeners.
			if( handlerCache.containsKey( event.name ) ){
				var list = handlerCache.get( event.name ).asArray();
				
				for( i in list ){

					if( list[i] === {} ){
						list.remove(i);
						continue;
					}
					
					that.bind( event.name, list[i] );
				}

				handlerCache.remove( event.name );
			}
		},

		fire : function( event ){
			if( !that.getSource(event.name) ){
				eventCache.put( event.name, event );
			}
			else
				return that.getSource(event.name).notify(event);
		},
		
		bindPattern : function( pattern, handler ){
			var keys = map.keys().asArray();
			
			for( i in keys ){
				
				if( keys[i].match(pattern) ){
					that.bind( keys[i], handler );
				}
			}
		},
		
		bind : function( event, handler ){

			if( map.containsKey(event) ){
				that.getSource(event).addObserver(event, handler);
			}
			
			// create a placeholder list
			else{
				handlerCache.put(event, handler);
			}
			
			if( eventCache.containsKey( event ) ){
				eventCache.add( event );
				var events = eventCache.get( event ).asArray();

				for( j in events )
					that.fire( events[j] );	

				eventCache.remove( event );
			}
		},
		
		bindTx : function( eventname, listener ){
			
			var suffixes = [".attempt", ".attempted", ".succeeded", ".failed"];

			for( i in suffixes ){
				var evt = eventname + suffixes[i];
				var method = methodName( evt );
				
				if( listener[method] ){
					that.bind(evt, listener[method]);
				}
			}
		},
		
		unbind : function( event, handler ){
			that.getSource(event).removeObserver( handler );
		},
		
		remove : function( event ){
			map.remove( map.get(event).event.name );
		},
		
		getSource : function( event ){
			return map.get(event) && map.get(event).source?map.get(event).source:null;
		}
	};

	return that;
};

org.jaxson.Observable = function(config){
	
	var observers = new org.jaxson.util.collections.MappedList();
	var events = new org.jaxson.util.collections.Map();
	var silent = false;
	var baseEvents = {};
	
	var that ={
		// disable firing
		silence : function( flag ){
			silent = flag;
		},
		
		// events
		removeEvent: function( name ){ events.remove( name ); },
		getEvents : function(){ return events.values().asArray(); },
		clearEvents: function(){ events.clear(); },
		setPriority: function( name, func, priority ){
			if( events.get(name) !== null ){
				var list = events.get(name);
			}
		},
		setVetoable: function( name, vetoable ){
			if( events.get(name) !== null )
				events.get(name).vetoable = vetoable;
		},

		addEvent: function( event ){
			
			if( event.events ){
				that.addEvent(event.events);
			}
			
			else{
				if( event.vetoable === undefined ) event.vetoable = false;
				
				if( event.global )
					EventDispatcher.register( event, that );
				
				events.put( event.name, event );
				
				if( event.global && event.tx && event.basename && !baseEvents[event.basename] )
					baseEvents[ event.basename ] = true;
			}
		},
		
		getBaseEvents : function(){ return baseEvents; },
		
		bindTxs : function(listener){
			for( i in baseEvents ){
				EventDispatcher.bindTx( i, listener );
			}
		},
		
		addEvents: function( array ){
			
			for( i in array ){
				
				var evt = array[i];

				if( evt.events ){
					for( j in evt.events ){
						that.addEvent(evt.events[j]);
					}
				}
				else
					that.addEvent( evt );
			}
		},
		
		// observer mgmt
		removeObserver : function( obs ){ observers.remove(obs); },
		clear : function(){ observers.clear(); },
		addObserver : function( name, obs ){
			
			var wrapper = {
				name: name,
				obs: obs
			};
			
			observers.put( name, wrapper); 
		},
		notify: function( event ){
			
			var list = observers.get(event.name);
			
			if( list === null && !silent )
				return true;
			
			if( list === null || silent )
				return false;
			
			var arr = list.asArray();
			
			for( i in arr ){
				var wrapper = arr[i];
				
				if( wrapper.name === event.name ){
					
					var evtdef = events.get(event.name);

					if( evtdef.vetoable === true && !silent ){
						var out = wrapper.obs(event);
						
						if( out === false )
							return out;
					}
					
					else if( evtdef.vetoable === false || evtdef.vetoable === undefined ){
						wrapper.obs(event);
					}
				}
			}
			
			if( evtdef.vetoable )
				return true;
		}
	};
	
	inherits( "JaxsonObject", "org.jaxson.Observable", that, config );
	
	return that;
};
